package com.zmykj.hik.hikService.Service.CMS;


import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.zmykj.hik.hikService.Common.CommonClass;
import com.zmykj.hik.hikService.Controllers.HikConfig;
import com.zmykj.hik.hikService.Service.Alarm.EAlarm;
import com.sun.jna.Native;


import com.zmykj.hik.hikService.Service.CMS.ISUPCMSByJNA.DEVICE_REGISTER_CB;
import com.zmykj.hik.hikService.Service.CMS.ISUPCMSByJNA.NET_EHOME_CMS_LISTEN_PARAM;
import com.zmykj.hik.hikService.Service.CMS.ISUPCMSByJNA.NET_EHOME_DEV_REG_INFO_V12;
import com.zmykj.hik.hikService.Common.CommonClass.NET_EHOME_DEV_SESSIONKEY;
import com.zmykj.hik.hikService.Common.CommonClass.NET_EHOME_LOCAL_ACCESS_SECURITY;
import com.zmykj.hik.hikService.Common.CommonClass.StringPointer;
import com.zmykj.hik.hikService.Service.Storage.ESS;
import com.zmykj.hik.hikService.Common.CommonMethod;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.zmykj.hik.rocketMQ.RocketConfig;
import com.zmykj.hik.rocketMQ.RocketMqProducer;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


public class ECMS {

    private boolean m_bIsRunning = false;
    private static ISUPCMSByJNA m_CMSInstance = null;
	private static FRegisterCallBack m_fRegisterCallBack = null; // register callback function achieve
	public static NET_EHOME_CMS_LISTEN_PARAM m_struCMSListenPara = new NET_EHOME_CMS_LISTEN_PARAM();
	private NativeLong m_lCMSListenHandle; // CMS listen handle

	/*
	    Before realizing the device function through ISUPSDK, you must first log in to the device through the web page to
    enable the ISUP function, and register on the Central Management Service (CMS)
    */
	public ECMS() {
	    if(m_CMSInstance == null) {
            if(!CreateCMSInstance())
            {
                System.out.println("Load CMS fail");
                return;
            }
        }
        if(!m_CMSInstance.NET_ECMS_Init()) {
            CommonMethod.logRecord("ERROR", "[NET_ECMS_Init]->EHomeCMS initiate failed, errCode: " + m_CMSInstance.NET_ECMS_GetLastError());
        }else{
            CommonMethod.logRecord("INFO", "[NET_ECMS_Init]->EHomeCMS initiate Successfully!");

            // open sdk log
            m_CMSInstance.NET_ECMS_SetLogToFile(3, "./EHomeSdkLog", true);

            CommonMethod.ByteCopy(CommonMethod.GetListenInfo().struCMSListenParam.struAddress.szIP, m_struCMSListenPara.struAddress.szIP);
            m_struCMSListenPara.struAddress.wPort = CommonMethod.GetListenInfo().struCMSListenParam.struAddress.wPort;
            m_lCMSListenHandle = new NativeLong(-1);

            if (m_fRegisterCallBack == null) {
                m_fRegisterCallBack = new FRegisterCallBack();
                m_struCMSListenPara.fnCB = m_fRegisterCallBack;
            }

            //Because the dynamic library path in the Linux environment is the default (/usr/lib), it is recommended to
            // configure the component library path here
            if (CommonMethod.isLinux()) {
                String sLibComPath = CommonMethod.GetLibPathByArch() + "HCAapSDKCom/";
                StringPointer strPointer = new StringPointer(sLibComPath);
                strPointer.write();
                if (m_CMSInstance.NET_ECMS_SetSDKLocalCfg(5, strPointer.getPointer()))
                    System.out.println("ComPath:" + sLibComPath);
                else
                    System.out.println("ComPath:" + sLibComPath);
                strPointer.read();
            }

            // Configure device access security level
            // “byAccessSecurity = 0” means that devices of all protocol versions can be accessed
            NET_EHOME_LOCAL_ACCESS_SECURITY struAccessSecure = new NET_EHOME_LOCAL_ACCESS_SECURITY();
            struAccessSecure.byAccessSecurity = 0;
            struAccessSecure.dwSize = struAccessSecure.size();
            struAccessSecure.write();
            m_CMSInstance.NET_ECMS_SetSDKLocalCfg(0, struAccessSecure.getPointer());

            m_lCMSListenHandle = m_CMSInstance.NET_ECMS_StartListen(m_struCMSListenPara);
            if (m_lCMSListenHandle.longValue() == -1) {
                m_CMSInstance.NET_ECMS_StopListen(m_lCMSListenHandle);
                CommonMethod.logRecord("ERROR", "[NET_ECMS_StartListen]->CMS start listen failed, errorCode is"
                        + m_CMSInstance.NET_ECMS_GetLastError());
            } else {
                m_bIsRunning = true;
                CommonMethod.logRecord("INFO", "[NET_ECMS_StartListen]->CMS start listen Successfully");
            }
        }
	}

	public void StopEcms()
    {
		if (m_lCMSListenHandle.longValue() >= 0)
		{
			boolean bStopListen = m_CMSInstance.NET_ECMS_StopListen(m_lCMSListenHandle);
			if (!bStopListen)
			{
				CommonMethod.logRecord("ERROR", "[NET_ECMS_StopListen]->CMS stop listen failed, errorCode is:"
						+ m_CMSInstance.NET_ECMS_GetLastError());
			}
			else
			{
			    m_bIsRunning = false;
				CommonMethod.logRecord("INFO", "[NET_ECMS_StopListen]->CMS stop listen successfully");
			}
		}
        m_CMSInstance.NET_ECMS_Fini();
	}

	//EhomeKey is a tool for mutual authentication between the device and the CMS, which is only used when the device
    // protocol version is 5.0
	public boolean Ehome50Auth(String sDeviceID, String sEhomeKey){
		for(int i = 0; i < OnlineDevManager.g_Devs.size(); i++)
		{
			String dev_DevID = CommonMethod.byteToString(OnlineDevManager.g_Devs.get(i).m_struDevInfo.struRegInfo.byDeviceID);
			if(dev_DevID.equals(sDeviceID))
			{
			    //Traverse the list of registered devices and perform EhomeKey verification on the device with the target DeviceID
				OnlineDevManager.g_Devs.get(i).byEhomeKey = sEhomeKey.getBytes();
			}
		}

		//The function of NET_ESS_HAMSHA256 is to encrypt DeviceID and EhomeKey through SHA256 to generate a string of
        // keys, which will be used as SecretKey when the image server is cloud storage. Not used at other times.
		StringPointer sSecKeyOut = new StringPointer(255);
		sSecKeyOut.write();
		boolean bRet = ESS.GetSSInstance().NET_ESS_HAMSHA256(sDeviceID, sEhomeKey, sSecKeyOut.getPointer(), 255);
		if(bRet){
			sSecKeyOut.read();
			System.arraycopy(sSecKeyOut.sData, 0, CommonMethod.GetListenInfo().struSSListenParam.szSecretKey, 0, 64);
			CommonMethod.logRecord("INFO", "[NET_ESS_HAMSHA256]->Cloud SerectKey Config Successfully" + 
					CommonMethod.byteToString(sSecKeyOut.sData));
		}else{
			CommonMethod.logRecord("ERROR", "[NET_ESS_HAMSHA256]->Cloud SerectKey Config Error: " +
                    ESS.GetSSInstance().NET_ESS_GetLastError());
		}
		return bRet;
	}

	//The status of the device will be reported through this callback function
	public class FRegisterCallBack implements DEVICE_REGISTER_CB {
		public boolean invoke(final NativeLong lUserID, int dwDataType, Pointer pOutBuffer, int dwOutLen, Pointer pInBuffer,
							  int dwInLen, Pointer pUser) {
			try {
			    //Load the callback device information through NET_EHOME_DEV_REG_INFO_V12
				String sClassName = NET_EHOME_DEV_REG_INFO_V12.class.getName();
				switch (dwDataType) {
				// Filter the device to register according to the device ID, if the device is not allowed to be
                // registered, it will return FALSE
				case ISUPCMSByJNA.ENUM_DEV_ON: {
				    //pOutBuffer points to the device registration information structure NET_EHOME_DEV_REG_INFO_V12
                    //pInBuffer points to the service information structure NET_EHOME_SERVER_INFO_V50
					// copy device RegisterInfo to local by reflection
					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
					// get Device protocol version
					int dwDevProVersion = strDevRegInfo.struRegInfo.byDevProtocolVersion[0];

					// take Device register information post to platform
					String sCurDeviceID = CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID);
					System.out.println("New Device is online, DevID is " + sCurDeviceID);
					// if DevList already Current Device, only take the place of the lUserID
					boolean bDevAdd = true;
					int iDevsSize = OnlineDevManager.g_Devs.size();
					for (int i = 0; i < OnlineDevManager.g_Devs.size(); i++) {
						String slocalDevID = CommonMethod.byteToString(OnlineDevManager.g_Devs.get(i).m_struDevInfo.struRegInfo.byDeviceID);
						String sDevInDevID = CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID);
						//Because the device has been put into the device list during Ehome50Auth before,
                        // if the devices that are now online are all ISUP5.0 devices
						if (slocalDevID.equals(sDevInDevID)) { // ISUP5.0
							OnlineDevManager.g_Devs.get(i).m_lLoginID = lUserID;
							dwDevProVersion = OnlineDevManager.g_Devs.get(i).dwVersion;
                            //CommonMethod.GetServerInfo().struTCPAlarmSever.wPort = CommonMethod.GetListenInfo().struCMSListenParam.struAddress.wPort;
							System.out.println("\n new UserID:" + OnlineDevManager.g_Devs.get(i).m_lLoginID);
							bDevAdd = false;
						}
					}

					// else add current device to devList
					if (bDevAdd) {
                        OnlineDevManager.DevAdd();
						iDevsSize = OnlineDevManager.g_Devs.size();
						OnlineDevManager.g_Devs.get(iDevsSize - 1).m_lLoginID = lUserID;
						OnlineDevManager.g_Devs.get(iDevsSize - 1).m_struDevInfo = strDevRegInfo;
					}



					CommonClass.NET_EHOME_SERVER_INFO_V50 strEhomeServerInfo = new CommonClass.NET_EHOME_SERVER_INFO_V50();
					strEhomeServerInfo.read();
					//strEhomeServerInfo.dwSize = strEhomeServerInfo.size();
					byte [] byIP = HikConfig.FWIP.getBytes();
					System.arraycopy(byIP, 0, strEhomeServerInfo.struUDPAlarmSever.szIP, 0, byIP.length);
					System.arraycopy(byIP, 0, strEhomeServerInfo.struTCPAlarmSever.szIP, 0, byIP.length);
					strEhomeServerInfo.dwAlarmServerType = HikConfig.AMS_ByProtocolType; //报警服务器类型：0- 只支持UDP协议上报，1- 支持UDP、TCP两种协议上报 2-MQTT
					strEhomeServerInfo.struTCPAlarmSever.wPort = HikConfig.AMS_PORT.shortValue();
					strEhomeServerInfo.struUDPAlarmSever.wPort = HikConfig.AMS_PORT.shortValue();

					byte [] byClouldAccessKey = HikConfig.KMS_AccessKey.getBytes();
					System.arraycopy(byClouldAccessKey, 0, strEhomeServerInfo.byClouldAccessKey, 0, byClouldAccessKey.length);
					byte [] byClouldSecretKey = HikConfig.KMS_SecretKey.getBytes();
					System.arraycopy(byClouldSecretKey, 0, strEhomeServerInfo.byClouldSecretKey, 0, byClouldSecretKey.length);

					strEhomeServerInfo.dwPicServerType = HikConfig.AMS_DwPicServerType;
					System.arraycopy(byIP, 0, strEhomeServerInfo.struPictureSever.szIP, 0, byIP.length);
					strEhomeServerInfo.struPictureSever.wPort = HikConfig.KMS_PORT.shortValue();
					strEhomeServerInfo.write();
					dwInLen = strEhomeServerInfo.size();
					pInBuffer.write(0,strEhomeServerInfo.getPointer().getByteArray(0, dwInLen),0,dwInLen);



				/*	// copy local server info to device
                   CommonMethod.GetServerInfo().dwSize = CommonMethod.GetServerInfo().size();
                    dwInLen = CommonMethod.GetServerInfo().dwSize;
                    CommonMethod.GetServerInfo().write();
					CommonMethod.PointerCopy(CommonMethod.GetServerInfo().getPointer(), pInBuffer, dwInLen);*/

					JSONObject objDevInfo = new JSONObject();
					objDevInfo.put("DeviceID", sCurDeviceID);
					objDevInfo.put("DeviceIP", CommonMethod.byteToString(strDevRegInfo.struRegInfo.struDevAdd.szIP));
					objDevInfo.put("DeviceVersion", dwDevProVersion);
					objDevInfo.put("CurDeviceNum", iDevsSize);

					//设备上线发请求
					System.out.println("=====设备上线====");
					Map mqMap = new HashMap();
					mqMap.put("deviceID",sCurDeviceID);
					mqMap.put("status","1");
					mqMap.put("rocketMqKey", UUID.randomUUID().toString());
					RocketMqProducer.sendMsgIntime(RocketConfig.HIK_MESSAGE,RocketConfig.HIK_MESSAGE_REGISTER, mqMap.get("rocketMqKey").toString(),JSONObject.toJSONString(mqMap));
					//CommonMethod.sendString(WebSocketService.MessageType.DevAdd.value(), objDevInfo.toString());
					break;
				}
				case ISUPCMSByJNA.ENUM_DEV_SESSIONKEY_ERROR:{
					System.out.println("SessionKey active Error");
				}
				//Device dropped
				case ISUPCMSByJNA.ENUM_DEV_OFF: {
					System.out.println("=====设备下线====");
					for (int i = 0; i < OnlineDevManager.g_Devs.size(); i++) {
						if (OnlineDevManager.g_Devs.get(i).m_lLoginID.longValue() == lUserID.longValue()) {
							String sLogoutDeviceID = CommonMethod.byteToString(OnlineDevManager.g_Devs.get(i).m_struDevInfo.struRegInfo.byDeviceID);
							CommonMethod.logRecord("INFO","Device[" + sLogoutDeviceID + "] Offlined");
							OnlineDevManager.g_Devs.remove(i);
							//CommonMethod.sendString(WebSocketService.MessageType.DevLogout.value(), sLogoutDeviceID);

							Map mqMap = new HashMap();
							mqMap.put("deviceID",sLogoutDeviceID);
							mqMap.put("status","0");
							mqMap.put("rocketMqKey", UUID.randomUUID().toString());
							RocketMqProducer.sendMsgIntime(RocketConfig.HIK_MESSAGE,RocketConfig.HIK_MESSAGE_REGISTER, mqMap.get("rocketMqKey").toString(),JSONObject.toJSONString(mqMap));

							break;
						}
					}

					//Note: You must reopen a thread to offline the device, otherwise it may cause a crash
					new Thread(){
					    public void run() {
					        System.out.println("NET_ECMS_ForceLogout Enter");
                            m_CMSInstance.NET_ECMS_ForceLogout(lUserID);
                        }
                    }.start();

					break;
				}

				//For authentication of devices supporting ISUP5.0, EHomeKey must be configured
				case ISUPCMSByJNA.ENUM_DEV_AUTH: {
					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);

					for (int i = 0; i < OnlineDevManager.g_Devs.size(); i++) {
						String sDevID = CommonMethod.byteToString(OnlineDevManager.g_Devs.get(i).m_struDevInfo.struRegInfo.byDeviceID);
						String sDevSerial = CommonMethod.byteToString(OnlineDevManager.g_Devs.get(i).m_struDevInfo.struRegInfo.sDeviceSerial);
						if (sDevSerial.equals(CommonMethod.byteToString(strDevRegInfo.struRegInfo.sDeviceSerial))) {
							if (sDevID.equals(CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID))) {
								CommonMethod.WriteBuffToPointer(OnlineDevManager.g_Devs.get(i).byEhomeKey, pInBuffer);
								return true;
							} else {
								// if serilizeNum similar, but deviceID is
								// different��this is Device`s name was
								// changed��offline CurrentDevice go ahead
								OnlineDevManager.g_Devs.remove(i);
								return true;
							}
						}
					}
					// add 5.0 Device to DevList
					// take 5.0 Device CMS info post to platform, waitting
					// for
					// platform input EhomeKey and authorize
					String sDeviceID = CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID);
					Ehome50Auth(sDeviceID,"hik12345");

					CommonMethod.logRecord("INFO", "New Ehome5.0 Device is Registering, DevID is " + sDeviceID);
                    OnlineDevManager.DevAdd();
					int iDevsSize = OnlineDevManager.g_Devs.size();
					OnlineDevManager.g_Devs.get(iDevsSize - 1).dwVersion = 5;
					OnlineDevManager.g_Devs.get(iDevsSize - 1).m_lLoginID = new NativeLong(-1);
					OnlineDevManager.g_Devs.get(iDevsSize - 1).m_struDevInfo = strDevRegInfo;
					OnlineDevManager.g_Devs.get(iDevsSize - 1).byEhomeKey = "hik12345".getBytes();
					//CommonMethod.sendString(WebSocketService.MessageType.DevAdd5.value(), sDeviceID);
					break;
				}
				case ISUPCMSByJNA.ENUM_DEV_DAS_EHOMEKEY_ERROR: {
					System.out.println("EhomeKey is Error\n");
					CommonMethod.logRecord("Error", "EhomeKey is Error");
					// copy device RegisterInfo to local
                    //!!!!!!!!!!!!!!Currently does not support obtaining device information!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
//					String sDeviceID = CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID);
//					CommonMethod.sendString(WebSocketService.MessageType.EhomeKeyError.value(), sDeviceID);
					break;
				}
				//For authentication of devices supporting ISUP5.0, SessionKey must be configured
				case ISUPCMSByJNA.ENUM_DEV_SESSIONKEY: {
					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
					NET_EHOME_DEV_SESSIONKEY struSessionKey = new NET_EHOME_DEV_SESSIONKEY();
					System.arraycopy(strDevRegInfo.struRegInfo.byDeviceID, 0, struSessionKey.sDeviceID, 0, 256);
					System.arraycopy(strDevRegInfo.struRegInfo.bySessionKey, 0, struSessionKey.sSessionKey, 0, 16);
					System.out.println("Get SessionKey: " + CommonMethod.byteToString(struSessionKey.sSessionKey));
					// struSessionKey.write();
                    //If AMS and CMS are installed in the same program, please call NET_EALARM_SetDeviceSessionKey directly
                    Boolean ifKeySet = m_CMSInstance.NET_ECMS_SetDeviceSessionKey(struSessionKey);
					//System.out.println("设置设备key==="+ifKeySet);
                    //If AMS and CMS are not installed in the same program, please transfer SessionKey to AMS first, and then add
					Boolean ifKeySetB  = EAlarm.GetAlarmInstance().NET_EALARM_SetDeviceSessionKey(struSessionKey);
					//System.out.println("设置报警key==="+ifKeySetB);
					break;
				}
				//During registration, the device requests DAS information(ISUP5.0 only)
				case ISUPCMSByJNA.ENUM_DEV_DAS_REQ: {
					try {
						JSONObject jsonDas = new JSONObject();
						jsonDas.put("Type", "DAS");
						JSONObject jsonDasInfo = new JSONObject();
						String sIP = CommonMethod.byteToString(m_struCMSListenPara.struAddress.szIP);
						int wPort = m_struCMSListenPara.struAddress.wPort;
						jsonDasInfo.put("Address",HikConfig.FWIP);
						//jsonDasInfo.put("Domain", "test.ys7.com");
						jsonDasInfo.put("ServerID", "das_" + sIP + "_" + wPort);
						jsonDasInfo.put("Port", wPort);
						jsonDasInfo.put("UdpPort", wPort);
						jsonDas.put("DasInfo", jsonDasInfo);
						System.out.println(jsonDasInfo.toString());
						String sDasInfo = jsonDas.toString();
						pInBuffer.write(0, sDasInfo.getBytes(), 0, sDasInfo.length());
					} catch (JSONException e) {
						e.printStackTrace();
					}
					break;
				}
				//When the device information changes, re-register the registered device
				case ISUPCMSByJNA.ENUM_DEV_DAS_REREGISTER: {
					if(pInBuffer == null)
					{
						return false;
					}
					
					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
					String sDevProtocolVersion = CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDevProtocolVersion);
					if(sDevProtocolVersion.equals("2"))
						break;				
					
					break;
				}
				//Device IP address changes (for example, when the device is in a mobile network or across base stations)
				case ISUPCMSByJNA.ENUM_DEV_ADDRESS_CHANGED: {
					NET_EHOME_DEV_REG_INFO_V12 strDevRegInfo = (NET_EHOME_DEV_REG_INFO_V12) CommonMethod.WritePointerDataToClass(pOutBuffer, sClassName);
					//print Device info
                    System.out.println("[Device IP Changed] DeviceID" + CommonMethod.byteToString(strDevRegInfo.struRegInfo.byDeviceID) +
                            " New IP" + CommonMethod.byteToString(strDevRegInfo.struRegInfo.struDevAdd.szIP));
					break;
				}
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			return true;
		}
	}

    private boolean CreateCMSInstance()
    {
        if(m_CMSInstance == null)
        {
            synchronized (ISUPCMSByJNA.class)
            {
                String strDllPath = "";
                try
                {
                    //System.setProperty("jna.debug_load", "true");
                    if(CommonMethod.isWindows())
                        strDllPath = CommonMethod.GetLibPathByArch() + "HCISUPCMS";
                    else if(CommonMethod.isLinux())
                        strDllPath = CommonMethod.GetLibPathByArch() + "libHCISUPCMS.so";
                    m_CMSInstance = (ISUPCMSByJNA) Native.loadLibrary(strDllPath, ISUPCMSByJNA.class);
                }catch (Exception ex) {
                    System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    public static ISUPCMSByJNA GetCMSInstance()
    {
        return m_CMSInstance;
    }

    public boolean IsRunning(){return m_bIsRunning;}
}
